package kAgent;

import kGeom.*;

public abstract class kAgent {
	public kVec         acc;
	public kVec         vel;
	public kVec         pos;
	
	public kAgent[] 	neighbors;
	
	public float        maxVel;
	public final static float defaultMaxVel = 1.0f;
	public float        maxForce;
	public final static float defaultMaxForce = 1.0f;
	public float        rangeOfVision;
	public final static float defaultRangeOfVision = 10.0f;
	public float        rangeOfComfort;
	public final static float defaultRangeOfComfort = 5.0f;
	
	public float        sepScale;
	public final static float defaultSepScale = 7.0f;
	public float        aliScale;
	public final static float defaultAliScale = 0.7f;
	public float        cohScale;
	public final static float defaultCohScale = 0.6f;

	public float        ht = 60*2;

	public kRange rebirthRange;
	public final static kRange defaultRebirthRange = new kRange(10);
	public kRange boundsRange;
	public final static kRange defaultBoundsRange = new kRange(100);
	  
	public kPointCloud posHistory;
	public kPointCloud velHistory;

	  
	  // constructor simple
	  public kAgent(){
	    // default values
		maxVel = defaultMaxVel;
		maxForce = defaultMaxForce;
		sepScale = defaultSepScale;
		aliScale = defaultAliScale;
		cohScale = defaultCohScale;
		rangeOfVision = defaultRangeOfVision;
		
		rebirthRange = defaultRebirthRange;
		boundsRange = defaultBoundsRange;
		
		init();
	  } 
	  
	  // ABSTRACT METHODS
	  // (must be overridden)
	  //
	  
	  /**
		 * EXAMPLE
		 * public void step(kAgent[] pop){
		 * updateNeighborhood(pop);
		 * updateAcc(pop);
		 * updatePos();
		 * borders();
		 * }
		 */
	  public abstract void step(kAgent[] pop);
	  
	  
	  /**
		 * EXAMPLE
		 * public void drawToSpace(){
		 * 		space.append(new kLine(pos, vel, vel.length()*2));
      	 *		space.append(pos);
		 * }
		 */
	  public abstract void drawToSpace(kElementBuffer space);
	  
	  
	  /**
		 * EXAMPLE
		 * public void influenceAcc(kAgent[] pop){
		 *	    	// call population functions
		 *		    kVec example = separate(pop);
		 *	
		 *		    // add the vector to acceleration
		 *		    acc.plus(example);
		 * }
		 */
	  public abstract void influenceAcc(kAgent[] pop);
	  
	  // NOT AS ABSTRACT METHODS
	  // (may be overridden)
	  //
	  
	  /**
		 * at a minimum, must reinitialize posHistory & velHistory
		 * and must set acc = new kVec()
		 */
	  public void init(){
		  acc = new kVec();
		  posHistory = new kPointCloud();
		  velHistory = new kPointCloud();
		    vel = kVec.randomVec(-1, 1);
		    pos = kVec.randomVec(rebirthRange);
		    
		    neighbors = new kAgent[0];
	  }
	  
	  public void borders() {
		    if (!boundsRange.containsPoint(pos)) {
		    	init();
		    	pos = kVec.randomVec(rebirthRange);
		    }
		  }



	  
	  
	  
	  

	  // FINAL METHODS
	  // (may not be overridden)
	  //
	  
	  public final void updateNeighborhood(kAgent[] pop){
		  neighbors = new kAgent[0];
		  for (int i = 0 ; i < pop.length; i++) {
			  kAgent other = pop[i];
			  float dist = pos.distance(other.pos);
			  if ((dist > 0) && (dist < rangeOfVision)) {
				  kAgent[] temp = new kAgent[neighbors.length + 1];
				  for (int n = 0; n < neighbors.length; n++) {
					  temp[n] = neighbors[n];
				  }
				  temp[neighbors.length] = other;
				  neighbors = temp;
			  }
		  }
	  }
	  
	  public final int neighborCount(){return neighbors.length;}
	  
	  /**
		 *
	 * is responsible for calculating various steering vectors, and using them to influence the acc vector
		 */
	  public final void updateAcc(kAgent[] pop){
	    // call population functions
	    kVec sep = separate();   
	    kVec ali = align();      
	    kVec coh = cohesion();

	    // add the vectors to acceleration
	    acc.plus(sep);
	    acc.plus(ali);
	    acc.plus(coh);
	    
	    influenceAcc(pop);
	  }
	  
	  
	  /**
	   * adds acceleration to velocity
		 * limits velocity to maxVel
		 * moves position by velocity
		 * resets acceleration to zero vec
		 */
	  public final void updatePos(){
		    vel.plus(acc);
		    vel.limit(maxVel);  
		    pos.plus(vel);
		    posHistory.append(pos);
		    velHistory.append(vel);
		    acc = new kVec(0,0,0);
		  }
	  

	  // separation
	  public final kVec separate () {
	    kVec sum = new kVec(0,0,0);
	    int count = 0;

	    for (int i = 0 ; i < neighbors.length; i++) {
	      kAgent other = neighbors[i];
		  float dist = pos.distance(other.pos);
		  if ((dist > 0) && (dist < rangeOfComfort)) {
	        kVec diff = kVec.clone(pos); 
	        diff.minus(other.pos);
	        diff.normalize();
	        diff.scale(1/dist);          
	        sum.plus(diff);
	        count++;    
		  }
	    }
	    if (count > 0) {
	      sum.scale(1/(float)count);
	    }
	    sum.scale(sepScale);
	    return sum;
	  }


	  // alignment
	  public final kVec align () {
	    kVec sum = new kVec(0,0,0);
	    int count = 0;
	    for (int i = 0 ; i < neighbors.length; i++) {
	      kAgent other = neighbors[i];
	        sum.plus(other.vel);
	        count++;
	    }
	    if (count > 0) {
	      sum.scale(1/(float)count);
	      sum.limit(maxForce);
	    }
	    sum.scale(aliScale);
	    return sum;
	  }

	  // cohesion
	  public final kVec cohesion () {
	    kVec sum = new kVec(0,0,0);   
	    int count = 0;
	    for (int i = 0 ; i < neighbors.length; i++) {
	        sum.plus(neighbors[i].pos); 
	        count++;
	    }
	    if (count > 0) {
	      sum.scale(1/(float)count);
	      return kVec.scale(steer(sum, ht),cohScale);
	    }
	    return new kVec(0,0,0);
	  }
	  
	  // steer
	  public final kVec steer(kVec target, float threshold) {
	    target.minus(pos); 
	    float dist = target.length();

	    if (dist > 0 && dist < threshold) {
	      target.normalize();
	      target.scale(maxVel);
	      target.minus(vel); 
	      target.limit(maxForce); 
	    } 
	    else {
	      target = new kVec(0,0,0);
	    }
	    return target;
	  }

	  
	  
}
